﻿using System.IO;
using System;

namespace Picnic.Web.FastCGI
{
    public class FastCGIRecordsReader : DisposableObject
    {
        private readonly byte[] _buff = new byte[8192];
        private Stream _stream;
        private int _buffLength;
        private int _buffOffset;

        public FastCGIRecordsReader(Stream stream)
        {
            _stream = stream;
        }

        public FastCGIRecord Read()
        {
            var version = ReadVersion();
            var type = ReadRecordType();
            var requestId = ReadRequestId();
            var contentLength = ReadContentLength();
            var paddingLength = ReadPaddingLength();
            ReadByte();
            var content = ReadContent(contentLength);

            SkipPaddingBytes(paddingLength);

            return new FastCGIRecord(version, type, requestId, contentLength, content);
        }

        private int ReadVersion()
        {
            return ReadByte();
        }

        private FastCGIRecordType ReadRecordType()
        {
            return (FastCGIRecordType)ReadByte();
        }

        private int ReadRequestId()
        {
            var requestIdByte1 = ReadByte();
            var requestIdByte2 = ReadByte();
            var result = (requestIdByte1 << 8) + requestIdByte2;

            return result;
        }

        private int ReadContentLength()
        {
            var contentLength1 = ReadByte();
            var contentLength2 = ReadByte();
            var result = (contentLength1 << 8) + contentLength2;

            return result;
        }

        private int ReadPaddingLength()
        {
            return ReadByte();
        }

        private byte[] ReadContent(int contentLength)
        {
            var content = new byte[contentLength];
            if (contentLength == 0)
            {
                return content;
            }

            var offset = 0;

            while (true)
            {
                if (_buffLength == 0)
                {
                    _buffLength = _stream.Read(_buff, 0, _buff.Length);
                    _buffOffset = 0;
                }

                var length = Math.Min(contentLength - offset, _buff.Length);

                Array.Copy(_buff, _buffOffset, content, offset, length);

                offset += length;
                _buffLength -= length;
                _buffOffset += length;

                if (offset == contentLength)
                {
                    return content;
                }
            }
        }

        private void SkipPaddingBytes(int bytesCount)
        {
            bytesCount.Times(() => ReadByte());
        }

        private byte ReadByte()
        {
            EnsureThatBuffIsNotEmpty();

            var result = _buff[_buffOffset];

            _buffLength--;
            _buffOffset++;

            return result;
        }

        private void EnsureThatBuffIsNotEmpty()
        {
            if (_buffLength == 0)
            {
                _buffLength = _stream.Read(_buff, 0, _buff.Length);
                _buffOffset = 0;
            }
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                _stream.Dispose();
                _stream = null;
            }

            base.Dispose(disposing);
        }
    }
}